package com.hdu.kdk_feign.registry;


import com.hdu.kdk_feign.annotation.EnableFeignClients;
import com.hdu.kdk_feign.annotation.FeignClient;
import com.hdu.kdk_feign.factoryBean.FeignFactoryBean;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.ClassUtils;

import java.util.*;

import static java.util.Objects.requireNonNull;
import static java.util.Optional.ofNullable;

public class FeignClientBeanDefinitionRegistry implements ImportBeanDefinitionRegistrar {


    private static final String BASE_PACKAGE_1 = "basePackage";
    private static final String BASE_PACKAGE_2 = "value";


    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        ClassPathBeanDefinitionScanner scanner = new FeignClientsScanner(registry);
        scanner.addIncludeFilter(
                new AnnotationTypeFilter(FeignClient.class)
        );
        String[] basePackages = getBasePackages(importingClassMetadata);
        for (String basePackage : basePackages) {
            for (BeanDefinition bd : scanner.findCandidateComponents(basePackage)) {
                doRegisterFactoryBeanDefinition(bd, registry);
            }
        }
    }

    private void doRegisterFactoryBeanDefinition(BeanDefinition bd, BeanDefinitionRegistry registry) {
        try {
            Class<?> clazz = Class.forName(bd.getBeanClassName());
            AbstractBeanDefinition factoryBeanDefinition = BeanDefinitionBuilder
                    .genericBeanDefinition(FeignFactoryBean.class)
                    .addConstructorArgValue(clazz).getBeanDefinition();
            registry.registerBeanDefinition(
                    requireNonNull(bd.getBeanClassName()),
                    factoryBeanDefinition
            );
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }


    private String[] getBasePackages(AnnotationMetadata importingClassMetadata) {

        Set<String> basePackages = new HashSet<>();

        Map<String, Object> attributes = importingClassMetadata
                .getAnnotationAttributes(EnableFeignClients.class.getName());

        ofNullable(attributes).ifPresent(as -> {
            ofNullable(as.get(BASE_PACKAGE_1)).ifPresent(packages ->
                    basePackages.addAll(Arrays.asList(((String[]) packages))));
            ofNullable(as.get(BASE_PACKAGE_2)).ifPresent(packages ->
                    basePackages.addAll(Arrays.asList(((String[]) packages))));
        });

        if (basePackages.isEmpty()) {
            basePackages.add(
                    ClassUtils.getPackageName(
                            ((StandardAnnotationMetadata) importingClassMetadata).getIntrospectedClass()
                    )
            );
        }

        return basePackages.toArray(new String[0]);
    }


    static class FeignClientsScanner extends ClassPathBeanDefinitionScanner {

        public FeignClientsScanner(BeanDefinitionRegistry registry) {
            super(registry);
        }

        @Override
        protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
            AnnotationMetadata metadata = beanDefinition.getMetadata();
            // 必须是接口
            return metadata.isInterface();
        }
    }
}
